using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Reflection;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.XPath;
using NBear.Common.JSON;

namespace NBear.Common
{
    /// <summary>
    /// KeyValueCollection
    /// </summary>
    [Serializable]
    public class KeyValueCollection
    {
        private bool containsDuplicatedColumnMapping;

        public bool ContainsDuplicatedColumnMapping
        {
            get
            {
                return containsDuplicatedColumnMapping;
            }
            set
            {
                containsDuplicatedColumnMapping = value;
            }
        }

        public delegate object ParseValueHandler(string s);

        #region Private Members

        private string[] keys;
        private object[] values;
        private Type[] types;
        private ParseValueHandler[] parsers;
        private string ParseString(string s)
        {
            return s;
        }
        private object ParseInt(string s)
        {
            return int.Parse(s);
        }
        private object ParseLong(string s)
        {
            return long.Parse(s);
        }
        private object ParseShort(string s)
        {
            return short.Parse(s);
        }
        private object ParseByte(string s)
        {
            return byte.Parse(s);
        }
        private object ParseBool(string s)
        {
            return bool.Parse(s);
        }
        private object ParseDecimal(string s)
        {
            return decimal.Parse(s);
        }
        private object ParseChar(string s)
        {
            return char.Parse(s);
        }
        private object ParseSbyte(string s)
        {
            return sbyte.Parse(s);
        }
        private object ParseFloat(string s)
        {
            return float.Parse(s);
        }
        private object ParseDouble(string s)
        {
            return double.Parse(s);
        }
        private object ParseByteArray(string s)
        {
            return Convert.FromBase64String(s);
        }
        private object ParseGuid(string s)
        {
            return new Guid(s);
        }
        private object ParseDateTime(string s)
        {
            return DateTime.Parse(s);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="T:KeyValueCollection"/> class.
        /// </summary>
        private KeyValueCollection()
        {
        }

        private void InitKeyValueCollection(PropertyInfo[] pis, Dictionary<string, string> propertyToColumn)
        {
            containsDuplicatedColumnMapping = propertyToColumn.Count > 0;

            keys = new string[pis.Length];
            values = new object[pis.Length];
            types = new Type[pis.Length];
            parsers = new ParseValueHandler[pis.Length];

            for (int i = 0; i < pis.Length; i++)
            {
                keys[i] = (propertyToColumn.ContainsKey(pis[i].Name) ? propertyToColumn[pis[i].Name] : pis[i].Name);
                values[i] = Util.DefaultValue(pis[i].PropertyType);
                types[i] = pis[i].PropertyType;
                if (pis[i].PropertyType == typeof(string))
                {
                    parsers[i] = new ParseValueHandler(ParseString);
                }
                else if (pis[i].PropertyType == typeof(int))
                {
                    parsers[i] = new ParseValueHandler(ParseInt);
                }
                else if (pis[i].PropertyType == typeof(long))
                {
                    parsers[i] = new ParseValueHandler(ParseLong);
                }
                else if (pis[i].PropertyType == typeof(short))
                {
                    parsers[i] = new ParseValueHandler(ParseShort);
                }
                else if (pis[i].PropertyType == typeof(byte))
                {
                    parsers[i] = new ParseValueHandler(ParseByte);
                }
                else if (pis[i].PropertyType == typeof(bool))
                {
                    parsers[i] = new ParseValueHandler(ParseBool);
                }
                else if (pis[i].PropertyType == typeof(decimal))
                {
                    parsers[i] = new ParseValueHandler(ParseDecimal);
                }
                else if (pis[i].PropertyType == typeof(char))
                {
                    parsers[i] = new ParseValueHandler(ParseChar);
                }
                else if (pis[i].PropertyType == typeof(sbyte))
                {
                    parsers[i] = new ParseValueHandler(ParseSbyte);
                }
                else if (pis[i].PropertyType == typeof(float))
                {
                    parsers[i] = new ParseValueHandler(ParseFloat);
                }
                else if (pis[i].PropertyType == typeof(double))
                {
                    parsers[i] = new ParseValueHandler(ParseDouble);
                }
                else if (pis[i].PropertyType == typeof(byte[]))
                {
                    parsers[i] = new ParseValueHandler(ParseByteArray);
                }
                else if (pis[i].PropertyType == typeof(Guid))
                {
                    parsers[i] = new ParseValueHandler(ParseGuid);
                }
                else if (pis[i].PropertyType == typeof(DateTime))
                {
                    parsers[i] = new ParseValueHandler(ParseDateTime);
                }
                else
                {
                    parsers[i] = new ParseValueHandler(ParseString);
                }
            }
        }

        #endregion

        #region Public Members

        /// <summary>
        /// Initializes a new instance of the <see cref="T:KeyValueCollection"/> class.
        /// </summary>
        /// <param name="pis">The pis.</param>
        public KeyValueCollection(PropertyInfo[] pis, Dictionary<string, string> propertyToColumn)
        {
            if (pis != null)
            {
                InitKeyValueCollection(pis, propertyToColumn);
            }
            else
            {
                throw new EntityWithNoPropertiesException();
            }
        }

        /// <summary>
        /// Gets the keys.
        /// </summary>
        /// <param name="exceptKeys">The except keys.</param>
        /// <returns></returns>
        public string[] GetKeys(params string[] exceptKeys)
        {
            if (exceptKeys != null && exceptKeys.Length > 0)
            {
                List<string> retKeys = new List<string>();
                foreach (string key in keys)
                {
                    bool isExcept = false;
                    foreach (string exceptMember in exceptKeys)
                    {
                        if (key.Equals(exceptMember))
                        {
                            isExcept = true;
                            break;
                        }
                    }
                    if (!isExcept)
                    {
                        retKeys.Add(key);
                    }
                }
                return retKeys.ToArray();
            }
            else
            {
                return keys;
            }
        }

        public object[] GetValues()
        {
            return values;
        }

        public object[] GetValues(string[] keys)
        {
            if (keys != null && keys.Length > 0)
            {
                List<object> retValues = new List<object>();
                for (int i = 0; i < keys.Length; i++)
                {
                    retValues.Add(this[keys[i]]);
                }
                return retValues.ToArray();
            }
            else
            {
                return new object[0];
            }
        }

        public Type[] GetTypes()
        {
            return types;
        }

        public Type[] GetTypes(string[] keys)
        {
            if (keys != null && keys.Length > 0)
            {
                List<Type> retTypes = new List<Type>();
                for (int i = 0; i < keys.Length; i++)
                {
                    for (int j = 0; j < this.keys.Length; j++)
                    {
                        if (this.keys[j] == keys[i])
                        {
                            retTypes.Add(types[j]);
                            break;
                        }
                    }
                }
                return retTypes.ToArray();
            }
            else
            {
                return new Type[0];
            }
        }

        /// <summary>
        /// Clones this instance.
        /// </summary>
        /// <returns></returns>
        public KeyValueCollection Clone()
        {
            KeyValueCollection retKeyValues = new KeyValueCollection();

            retKeyValues.containsDuplicatedColumnMapping = this.containsDuplicatedColumnMapping;

            string[] cloneKeys = new string[keys.Length];
            keys.CopyTo(cloneKeys, 0);
            retKeyValues.keys = cloneKeys;

            object[] cloneValues = new object[values.Length];
            values.CopyTo(cloneValues, 0);
            retKeyValues.values = cloneValues;

            Type[] cloneTypes = new Type[types.Length];
            types.CopyTo(cloneTypes, 0);
            retKeyValues.types = cloneTypes;

            ParseValueHandler[] cloneParsers = new ParseValueHandler[parsers.Length];
            parsers.CopyTo(cloneParsers, 0);
            retKeyValues.parsers = cloneParsers;

            return retKeyValues;
        }

        /// <summary>
        /// Gets or sets the <see cref="T:Object"/> at the specified index.
        /// </summary>
        /// <value></value>
        public object this[int index]
        {
            get
            {
                if (index < 0 || index >= keys.Length)
                {
                    return null;
                }
                return values[index];
            }
            set
            {
                if (index > -1 && index < keys.Length)
                {
                    values[index] = value;

                    //check for duplicated column mapping (one column maping to 2+ properties)
                    if (containsDuplicatedColumnMapping)
                    {
                        for (int i = 0; i < keys.Length; i++)
                        {
                            if (i != index && keys[i] == keys[index])
                            {
                                values[i] = value;
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the <see cref="T:Object"/> at the specified index.
        /// </summary>
        /// <value></value>
        public object this[string key]
        {
            get
            {
                for (int i = 0; i < keys.Length; i++)
                {
                    if (keys[i].Equals(key))
                    {
                        return values[i];
                    }
                }
                return null;
            }
            set
            {
                for (int i = 0; i < keys.Length; i++)
                {
                    if (keys[i].Equals(key))
                    {
                        values[i] = value;
                        if (!containsDuplicatedColumnMapping)
                        {
                            break;
                        }
                    }
                }
            }
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < keys.Length; i++)
            {
                if (values[i] != null)
                {
                    sb.Append(string.Format("<{0}>{1}</{0}>", keys[i], types[i] == typeof(byte[]) ? Convert.ToBase64String((byte[])values[i]) : System.Web.HttpUtility.HtmlEncode(values[i].ToString())));
                }
            }

            return sb.ToString();
        }

        public void Load(string xml)
        {
            if (string.IsNullOrEmpty(xml))
            {
                return;
            }

            XmlReader reader = new XmlTextReader(new MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes("<root>" + xml + "</root>")));
            reader.ReadStartElement();

            Load(reader);
        }

        public void Load(XmlReader reader)
        {
            reader.ReadStartElement();
            
            int i = 0;
            do
            {
                while (reader.Name != keys[i])
                {
                    i++;
                }
                reader.ReadStartElement();
                values[i] = parsers[i](System.Web.HttpUtility.HtmlDecode(reader.ReadString()));
                reader.ReadEndElement();
                i++;

            }
            while (reader.NodeType != XmlNodeType.EndElement);

            reader.ReadEndElement();
        }

        public string ToJsonString()
        {
            JSONObject json = new JSONObject();

            for (int i = 0; i < keys.Length; i++)
            {
                json.put(keys[i], values[i]);
            }

            return json.ToString();
        }

        public void LoadJson(string sJson)
        {
            JSONObject json = new JSONObject(sJson);
            LoadJson(json);
        }

        public void LoadJson(JSONObject json)
        {
            for (int i = 0; i < keys.Length; i++)
            {
                values[i] = parsers[i](json.getString(keys[i]));
            }
        }

        #endregion
    }
}
